package datastore

import "code.google.com/p/appengine-go/appengine/datastore"

datastore提供App Engine的datastore服务。

Basic Operations

实体是存储的基本单元,并且与一个键关联。一个键包含一个可选的父键,一个AppID字符串,一个类型字符串(也被认为是实体的类型),以及一个字符串StringID或者数字IntID,StringID也被认为是实体的名称或者键的名称。

创建一个StringID和IntID都为零值的键是合法的,这种键被称作不完全键,和任何已保存的实体都不关联。将一个实体存在datastore里一个不完全键下时,就会生成一个对应于该实体的唯一键,该键包含一个非零的IntID。

一个实体的内容是从大小写敏感的名称到值的映射,合法的值类型如下:

- 有符号整数(int, int8, int16, int32 and int64),
- 布尔值,
- 字符串,
- 浮点数(float32, float64),
- []byte(最大1mb长度),
- 底层类型为以上类型的任意类型,
- *Key,
- time.Time(保存为微妙精度),
- appengine.BlobKey,
- 所有字段都是上述合法类型的结构体类型,
- 以上任一类型的切片.

结构体的切片是合法的,含有切片的结构体也合法。但是,如果结构包含另一个结构,那么最多允许重复一次。这就排除了递归的定义结构体类型:某结构体T(直接或间接的)包含[]T。Get和Put函数加载或保存实体的内容。一个实体的内容应使用一个结构体的指针表示。

示例:

type Entity struct {
	Value string
}
func handle(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	k := datastore.NewKey(c, "Entity", "stringID", 0, nil)
	e := new(Entity)
	if err := datastore.Get(c, k, e); err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	old := e.Value
	e.Value = r.URL.Path
	if _, err := datastore.Put(c, k, e); err != nil {
		http.Error(w, err.Error(), 500)
		return
	}
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	fmt.Fprintf(w, "old=%q\nnew=%q\n", old, e.Value)
}

GetMulti、PutMulti和DeleteMulti是Get、Put和Delete函数的批处理版本,他们接受[]*Key而不是*Key,并且在遭遇部分错误时,返回appengine.MultiError。

Properties

一个实体的内容可以被多个类型表示。一般应使用结构体的指针,但也可以是任何实现了PropertyLoadSaver接口的类型。如果使用结构体指针的话,就不需要显式的实现PropertyLoadSaver接口了,datastore会自动使用反射进行转换。当然了,如果你闲的蛋疼实现了该接口,那么就会优先使用该接口的方法。结构体指针更稳定更易于使用,相对的PropertyLoadSaver更灵活。

提供给Get和Put函数的实际类型不必相同,即使在不同请求中也没事。一般来说,任何实体都是保存为一系列属性,也是属性对属性的装载到目标值里。当装载进一个结构体指针时,如果实体不能被完整的表达出来(如缺失了某个字段),将会返回ErrFieldMismatch,但是调用者可以自行决定怎么处理该错误(记录/恢复/忽略)。

默认情况下,对于结构体的指针,所有属性都潜在的被索引,属性名一般和字段名一致(因此必须以大写字母起始)。字段可以使用标签,格式为`datastore:"name,options"`。标签名name为属性名,必须是一个或多个"."连接起来的Go标识符,但可以小写字母起始。空的标签名代表使用字段名,标签名为"-"代表忽略该字段。如果options是"noindex"表示该字段不应被索引,如果options是""则逗号可省略。不支持其他options。

字段(除了[]byte以外)都默认被索引。超过500字符的字符串不能被索引,用于保存长字符串的字段应使用标签注明options为"noindex"。

示例:

// A和B重命名为a和b
// A、C和J不被索引
// D的标签相当于没有标签(E)
// I会被datastore完全忽略
// J的标签信息既有datastore的,也有json包的
type TaggedStruct struct {
	A int `datastore:"a,noindex"`
	B int `datastore:"b"`
	C int `datastore:",noindex"`
	D int `datastore:""`
	E int
	I int `datastore:"-"`
	J int `datastore:",noindex" json:"j"`
}

Structured Properties

如果结构体内包含另一个结构体,那么嵌套/内嵌的结构体会被压平。如下例所示,下面给出的定义:

type Inner1 struct {
	W int32
	X string
}
type Inner2 struct {
	Y float64
}
type Inner3 struct {
	Z bool
}
type Outer struct {
	A int16
	I []Inner1
	J Inner2
	Inner3
}

结构体Outer产生的属性等价于如下的情况:

type OuterEquivalent struct {
	A     int16
	IDotW []int32  `datastore:"I.W"`
	IDotX []string `datastore:"I.X"`
	JDotY float64  `datastore:"J.Y"`
	Z     bool
}

如果Outer内嵌的Inner3字段设标签为`datastore:"Foo"`,那么等价的字段将是FooDotZ bool `datastore:"Foo.Z"`。

如果一个结构体字段标记为"noindex",它所有内含的字段压平后都会设为"noindex"。

The PropertyLoadSaver Interface

一个实体的内容也可以被任何实现了PropertyLoadSaver 接口的类型表示。该类型可以是结构体的指针,但也可以是别的类型。Datastore包会调用Load方法获取实体的内容,Save方法来保存实体的内容。合理的用法包括获取未存储的字段、验证字段和只在值为正时索引字段等。

示例:

type CustomPropsExample struct {
	I, J int
	// Sum未存储,但它应该总是等于I + J
	Sum int `datastore:"-"`
}
func (x *CustomPropsExample) Load(c <-chan Property) error {
	// 像通常那样获取I和J
	if err := datastore.LoadStruct(x, c); err != nil {
		return err
	}
	// 取得Sum字段的值
	x.Sum = x.I + x.J
	return nil
}
func (x *CustomPropsExample) Save(c chan<- Property) error {
	defer close(c)
	// 验证Sum字段
	if x.Sum != x.I + x.J {
		return errors.New("CustomPropsExample has inconsistent sum")
	}
	// 像通常那样保存I和J。
	// 下面的代码出于示范的目的手动执行,
	// 等价于调用"return datastore.SaveStruct(x, c)"。
	c <- datastore.Property{
		Name:  "I",
		Value: int64(x.I),
	}
	c <- datastore.Property{
		Name:  "J",
		Value: int64(x.J),
	}
	return nil
}

*PropertyList类型实现了PropertyLoadSaver,因此可以保存任意实体的内容。

Queries

使用实体的属性或键的父子关系进行检索。执行query时,输出结果的迭代器,键集或者键值对集。检索可以重用,并发安全。但是迭代器是并发不安全的。

查询是不可变的,要么是调用NewQuery创建,要么是现有的查询调用Filter或Order之类的方法产生的。一个query一般都是由调用NewQuery后跟一条零到多个的这些方法构成的。这些方法有:

- Ancestor和Filter 约束/过滤一个query返回的实体集
- Order 控制返回的实体集里实体的顺序
- Project 约束返回的字段
- Distinct 对映射构造的实体进行去重
- KeysOnly 让迭代器只返回键,而不返回键值对
- Start、End、Offset和Limit 定义匹配得到的实体集应返回的子序列
  Start和End接受Cursor类型,Offset和Limit接受整数
  Start和Offset控制第一个结果的位置,End和Limit控制最后一个结果的位置
  如果Start和Offset都设置了,offset以Start为基准
  如果End和Limit都设置了,那么第一个到达的起效,Limit以Start+Offset为基准,而非End
  如果limit为负数,表示不设约束

示例:

type Widget struct {
	Description string
	Price       int
}
func handle(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	q := datastore.NewQuery("Widget").
		Filter("Price <", 1000).
		Order("-Price")
	b := new(bytes.Buffer)
	for t := q.Run(c); ; {
		var x Widget
		key, err := t.Next(&x)
		if err == datastore.Done {
			break
		}
		if err != nil {
			serveError(c, w, err)
			return
		}
		fmt.Fprintf(b, "Key=%v\nWidget=%#v\n\n", key, x)
	}
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	io.Copy(w, b)
}

Transactions

RunInTransaction在一次事务中运行一个函数。

示例:

type Counter struct {
	Count int
}
func inc(c appengine.Context, key *datastore.Key) (int, error) {
	var x Counter
	if err := datastore.Get(c, key, &x); err != nil && err != datastore.ErrNoSuchEntity {
		return 0, err
	}
	x.Count++
	if _, err := datastore.Put(c, key, &x); err != nil {
		return 0, err
	}
	return x.Count, nil
}
func handle(w http.ResponseWriter, r *http.Request) {
	c := appengine.NewContext(r)
	var count int
	err := datastore.RunInTransaction(c, func(c appengine.Context) error {
		var err1 error
		count, err1 = inc(c, datastore.NewKey(c, "Counter", "singleton", 0, nil))
		return err1
	}, nil)
	if err != nil {
		serveError(c, w, err)
		return
	}
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	fmt.Fprintf(w, "Count=%d", count)
}

Index

Variables

var (
    // Get和Next之类的函数在接受的参数类型不合法时返回本错误
    ErrInvalidEntityType = errors.New("datastore: invalid entity type")
    // 当提供的键不合法时返回本错误
    ErrInvalidKey = errors.New("datastore: invalid key")
    // 当给出的键没有发现对应的实体时,返回本错误
    ErrNoSuchEntity = errors.New("datastore: no such entity")
)
var Done = errors.New("datastore: query has no more results")

当一个query的迭代器迭代结束时,返回Done。

var ErrConcurrentTransaction = errors.New("datastore: concurrent transaction")

当一个事务因为并发事务的冲突回滚时,将返回本错误。

type ErrFieldMismatch

type ErrFieldMismatch struct {
    StructType reflect.Type
    FieldName  string
    Reason     string
}

当字段类型不匹配,或者有字段未导出到目标结构体时返回本类型错误。StructType为提供的结构体的类型。

func (*ErrFieldMismatch) Error

func (e *ErrFieldMismatch) Error() string

type Key

type Key struct {
    // 内含隐藏字段
}

Key代表一个datastore保存的实体的键,是不可变的。

func DecodeKey

func DecodeKey(encoded string) (*Key, error)

解码一个不透明描述(*Key的Encode方法生成)来获取键。

func NewIncompleteKey

func NewIncompleteKey(c appengine.Context, kind string, parent *Key) *Key

创建一个不完全键,kind参数不能为空。

func NewKey

func NewKey(c appengine.Context, kind, stringID string, intID int64, parent *Key) *Key

创建一个新的键,kind参数不可为空。stringID和intID两个参数至少应有一个为零值。如果都为零值,则键为不完全键。Parent参数必须是完全键或者nil。

func (*Key) AppID

func (k *Key) AppID() string

返回键的AppID。

func (*Key) IntID

func (k *Key) IntID() int64

返回键的IntID,可以为0。

func (*Key) StringID

func (k *Key) StringID() string

返回键的StringID(也被视为实体的名字或键的名字),可以是""。

func (*Key) Kind

func (k *Key) Kind() string

返回键的类型(也被视为实体的类型)。

func (*Key) Parent

func (k *Key) Parent() *Key

返回键的父键,可以是nil。

func (*Key) Incomplete

func (k *Key) Incomplete() bool

返回键是否不完整,即是否键的StringID和IntID都为零值。

func (*Key) Namespace

func (k *Key) Namespace() string

返回键的命名空间。

func (*Key) Equal

func (k *Key) Equal(o *Key) bool

返回两个键是否相等。

func (*Key) String

func (k *Key) String() string

返回键的字符串表示。

func (*Key) Encode

func (k *Key) Encode() string

返回键的不透明描述,该描述可以用于HTML文件和URL中,并且和Python、Jave运行时环境兼容。

func (*Key) GobEncode

func (k *Key) GobEncode() ([]byte, error)

func (*Key) GobDecode

func (k *Key) GobDecode(buf []byte) error

func (*Key) MarshalJSON

func (k *Key) MarshalJSON() ([]byte, error)

func (*Key) UnmarshalJSON

func (k *Key) UnmarshalJSON(buf []byte) error

type Property

type Property struct {
    // Name是属性名
    Name string
    // Value是属性的值,合法类型如下
    //   - int64
    //   - bool
    //   - string
    //   - float64
    //   - *Key
    //   - time.Time
    //   - appengine.BlobKey
    //   - appengine.GeoPoint
    //   - []byte (最长1mb)
    // 这个集合比合法结构体字段的类型集合要小
    // 属性的值不能是切片(除了[]byte以外),应使用多个属性替代
    // 属性的类型必须显式的属于上面的列表,只是底层类型在该列表是不行的
    // 如"type myInt64 int64",myInt64类型就是非法的
    // 因此,比合法结构体字段类型要严格的多
    //
    // 当从索引装载实体时,如通过映射查询,Value会拥有不透明的类型
    // 当使用映射查询时,应该将实体装载到结构体中,而非PropertyLoadSaver接口中
    //
    // 一个Value也可以是nil的接口值;等价于Python的None,但不能被go结构体直接表示
    // 装载一个nil值的属性到结构体会将对应的字段设为零值
    Value interface{}
    // NoIndex控制datastore是否应索引该属性
    NoIndex bool
    // 控制实体是否可以包含多个同名的属性,用来表示类型为[]T而非T的字段的一系列值
    // 即使某个特定的实例的某个名称的属性只有一个,也应设置该Multiple为真
    Multiple bool
}

Property是附加了一些元数据的键值对。一个datastore的实体的内容都以Property序列的形式保存和加载。一个实体可以包含同名的多个Property,这些Property都应设值Multiple为真,表示一个切片。

type PropertyLoadSaver

type PropertyLoadSaver interface {
    Load(<-chan Property) error
    Save(chan<- Property) error
}

PropertyLoadSaver接口可以与[]Property类型相互转换。

type PropertyList

type PropertyList []Property

PropertyList为 []Property实现了PropertyLoadSaver接口。

func (*PropertyList) Load

func (l *PropertyList) Load(c <-chan Property) error

装载所有提供的属性到l,本函数不会先重置*l为空切片。

func (*PropertyList) Save

func (l *PropertyList) Save(c chan<- Property) error

保存l的所有属性为一个Property的切片。

type Query

type Query struct {
    // 内含隐藏字段
}

Query表示一个datastore的查询。

func NewQuery

func NewQuery(kind string) *Query

为某类型的实体创建一个新的Query。

kind为空表示返回所有实体,包括被其他App Engine产品创建和管理的实体,这被称为无类型实体,无类型实体不能对属性值进行过滤和排序。

func (*Query) Ancestor

func (q *Query) Ancestor(ancestor *Key) *Query

返回具有同一根键的一个衍生查询,过滤器。Ancestor参数不能为nil。

func (*Query) Filter

func (q *Query) Filter(filterStr string, value interface{}) *Query

返回一个对字段进行过滤的衍生查询。filterStr参数必须是一个字段名后跟可选的空格以及一个操作符,操作符为">"、"<"、">="、"<="和"="其中之一。字段根据操作符与提供的value进行比较。

func (*Query) EventualConsistency

func (q *Query) EventualConsistency() *Query

返回会输出具有最终一致性的结果的衍生查询,只对Ancestor方法有影响。

func (*Query) Project

func (q *Query) Project(fieldNames ...string) *Query

返回一个只输出给定字段的映射查询。

func (*Query) Distinct

func (q *Query) Distinct() *Query

返回对映射字段集合进行去重的一个衍生的映射查询,只能用于映射查询。

func (*Query) KeysOnly

func (q *Query) KeysOnly() *Query

返回一个只输出键的衍生的唯键查询,本方法不能用于映射查询。

func (*Query) Count

func (q *Query) Count(c appengine.Context) (int, error)

返回查询得到的结果的数量。

func (*Query) Order

func (q *Query) Order(fieldName string) *Query

返回一个根据参数进行排序后的查询。函数将按参数递增顺序排序的(从小到大输出),如果要反过来,在fieldName参数的字段名前添加'-'。

func (*Query) Start

func (q *Query) Start(c Cursor) *Query

返回一个从给定位置起始的查询。

func (*Query) End

func (q *Query) End(c Cursor) *Query

返回一个在给定位置结束的查询。

func (*Query) Limit

func (q *Query) Limit(limit int) *Query

返回一个限定结果个数的查询,limit小于0代表无限制。

func (*Query) Offset

func (q *Query) Offset(offset int) *Query

返回一个跳过起始offset个结果的查询,offset不能小于0。

func (*Query) Run

func (q *Query) Run(c appengine.Context) *Iterator

在给定的上下文环境下输出查询结果的迭代器。

func (*Query) GetAll

func (q *Query) GetAll(c appengine.Context, dst interface{}) ([]*Key, error)

方法在给定的上下文环境下执行查询,将结果附加进dst并返回所有对应的键。

对dst的要求和GetMulti函数一样。返回的键和附加进dst的实体一一对应,如果是唯键查询,会忽略dst。

type Iterator

type Iterator struct {
    // 内含隐藏字段
}

Iterator是一个查询返回的结果的迭代器。

func (*Iterator) Cursor

func (t *Iterator) Cursor() (Cursor, error)

返回迭代器当前位置的Cursor。

func (*Iterator) Next

func (t *Iterator) Next(dst interface{}) (*Key, error)

返回下一个结果的键,当没有下一个时,会返回Done作为错误值。

如果查询未使用KeysOnly,并且dst不是nil,方法还会将返回的key对应的实体装载到dst中,dst的要求和Get函数一样,也可能返回和Get函数一样的错误。

type Cursor

type Cursor struct {
    // 内含隐藏字段
}

Cursor 代表一个迭代器的位置。它可以编解码为一个不透明字符串。一个Cursor可以用于不同的HTTP请求,但是只有在类型、父键、过滤和顺序约束都一致的情况下才代表同一个位置。

func DecodeCursor

func DecodeCursor(s string) (Cursor, error)

解码一个表示Cursor的base64字符串获取该Cursor。

func (Cursor) String

func (c Cursor) String() string

返回一个Cursor的base64字符串表示。

type TransactionOptions

type TransactionOptions struct {
    // 表示是否事务跨越多个实体组。
    // 单实体组的事务指它使用的所有键都有同一个根键。
    // 注意跨组事务和单组事务的行为是不一样的。
    // 特别注意,在全局查询中,更倾向于出现跨组事务。
    // 即使单组事务,将XG设为真也是合法的;反过来就不行。
    XG bool
}

TransactionOptions是执行事务的选项。

func AllocateIDs

func AllocateIDs(c appengine.Context, kind string, parent *Key, n int) (low, high int64, err error)

AllocateIDs返回一个n个整数ID的范围,可用于和给出的kind和父键联合构建key。 kind不能为空,parent可以为nil。返回的范围内的整数ID不会被datastore键生成器所使用,可以用于NewKey函数而不会导致冲突。

合法的整数ID 要求不小于low且小于high。如果没有出错,返回值low + n == high。

func Get

func Get(c appengine.Context, key *Key, dst interface{}) error

函数将key对应的实体装载到dst,dst必须是结构体的指针或者实现了PropertyLoadSaver接口。如果没有对应key的实体,函数返回ErrNoSuchEntity。

dst中与实体不匹配的字段不会被修改,匹配的切片类型字段不会重置,对应的属性值直接添加到后面。因此,强烈建议对每次调用Get函数都使用指向零值结构体的指针。

如果某属性对应的字段类型错误时,或者某属性在dst结构体中无对应字段或字段隐藏时,返回ErrFieldMismatch类型错误。这个错误只会在dst是结构体指针时可能被返回。

func GetMulti

func GetMulti(c appengine.Context, key []*Key, dst interface{}) error

函数是Get函数的批处理版本。

dst必须是[]S、[]*S、[]I、[]P,其中S表示结构体类型,I表示接口类型,P表示实现了PropertyLoadSaver接口的某种类型,如果dst类型为[]I,其中每个元素都应为结构体指针或者实现了PropertyLoadSaver接口。

有一个特例,PropertyList类型不适用于dst,虽然该类型是结构体的切片,PropertyList类型视为非法以免当我们需要[]PropertyList时被错误的传递。

func Put

func Put(c appengine.Context, key *Key, src interface{}) (*Key, error)

将src保存到key对应的实体中,src必须是结构体指针或者实现了PropertyLoadSaver接口。如果是结构体指针,结构体的所有隐藏字段将忽略。如果key是不完全键,返回的键为datastore生成的对应该实体的专有完全键。

func PutMulti

func PutMulti(c appengine.Context, key []*Key, src interface{}) ([]*Key, error)

函数为Put的批处理版本,对src的要求同GetMulti的dst参数。

func Delete

func Delete(c appengine.Context, key *Key) error

删除一个键对应的实体。

func DeleteMulti

func DeleteMulti(c appengine.Context, key []*Key) error

Delete的批处理版本。

func LoadStruct

func LoadStruct(dst interface{}, c <-chan Property) error

从c中装载属性到dst,直到c关闭。dst必须是结构体的指针。

func SaveStruct

func SaveStruct(src interface{}, c chan<- Property) error

将src的属性保存到c,写完后会关闭c。src必须是结构体的指针。

func RunInTransaction

func RunInTransaction(c appengine.Context, f func(tc appengine.Context) error, opts *TransactionOptions) error

函数在一次事务中执行f。它使用一个事务上下文tc调用f,f应在全部的操作中都使用tc。

如果f返回nil,RunInTransaction会尝试递交事务,如果成功会返回nil。如果因为事务冲突递交失败,函数会重试f,每次重试都会重新产生一个新的事务上下文tc。在递交失败3次后,函数会放弃并返回ErrConcurrentTransaction。

如果f返回非nil的错误值,则datastore不会被修改,函数不会重试,而直接返回该错误。

注意当f返回时,事务还没有递交。调用代码必须注意不能假设f的任何操作已经被递交,除非函数返回了nil。

不支持嵌套事务,c只能是从user或appengine包获取的Context,不能是事务上下文。